package cn.tedu.tea.admin.server.core.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 统计所有Service方法执行耗时的切面类
 *
 * @author java@tedu.cn
 * @version 1.0
 */
@Slf4j
@Aspect // 标记当前类是一个切面类
@Component // 标记当前类是一个组件类，则Spring会进行处理
public class TimerAspect {

    // 连接点（JoinPoint）：程序执行过程中的某个节点，可能是某个方法的调整，或是某个方法抛出了异常
    // 切入点（PointCut）：匹配连接点的表达式
    // -------------------------------------------------------------------
    // 通知（Advice）注解：
    // @Around：环绕，可以包裹连接点的执行，所以，你可以在执行连接点之前和之后编写你所需的代码
    // @Before：在执行连接点之前
    // @After：在执行连接点之后
    // @AfterThrowing：在抛出异常之后
    // @AfterReturning：在成功返回之后
    // -------------------------------------------------------------------
    // 各通知的执行点大致是以下这样：
    // @Around开始
    // try {
    //      @Before
    //      执行连接点
    //      @AfterReturning
    // } catch (Throwable e) {
    //      @AfterThrowing
    // } finally {
    //      @After
    // }
    // @Around结束
    // -------------------------------------------------------------------
    // 切入点表达式：匹配某些方法
    // 可以使用通配符：
    // - 星号（*）：任意有且仅有1次匹配
    // - 2个连接的小数点（..）：任意n次匹配，仅能用于包名和参数列表
    //                 ↓ 返回值类型
    //                   ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 包名
    //                                                  ↓ 类名
    //                                                    ↓ 方法名
    //                                                      ↓↓ 参数列表
    // 在切入点表达式中，在返回值类型声明的左侧，还可以指定修饰符，修饰符是可选的
    // 如果没有显式的指定修饰符，则无论什么修饰符都可以匹配
    // 其实，其它部分也是如此，例如，如果没有指定包名，则无视匹配的类在哪个包下
    // 注意：注解是典型的修饰符
    // 注意：无论是注解，还是返回值、参数，只要不是基本数据类型、java.lang包下的类型，都必须使用全限定名
    @Around("execution(* cn.*.tea.admin.server..service.*.*(..))")
    //     ↓↓↓↓↓↓ 使用@Around时必须声明为ProceedingJoinPoint对象调用proceed()返回的结果
    //            ↓↓↓↓↓ 自定义的方法名
    //                  ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 使用@Around时必须是ProceedingJoinPoint类型
    //                                           ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 必须抛出
    public Object timer(ProceedingJoinPoint pjp) throws Throwable {
        log.trace("TimerAspect.timer()开始执行");
        // 获取连接点信息
        String targetClassName = pjp.getTarget().getClass().getName(); // 类型名
        String signatureName = pjp.getSignature().getName(); // 方法名
        Object[] args = pjp.getArgs(); // 参数列表
        System.out.println("连接点（方法）所属的类的名称：" + targetClassName);
        System.out.println("连接点（方法）的名称：" + signatureName);
        System.out.println("连接点（方法）的参数列表：" + Arrays.toString(args));
        // 获取起始时间
        long start = System.currentTimeMillis();
        // 调用ProceedingJoinPoint参数对象的proceed()方法，表示执行连接点，即调用了匹配到的方法
        // 注意-1：调用proceed()方法的异常，必须抛出，不可以简单的try...catch（除非在catch中再抛出异常）
        // 注意-2：调用proceed()方法时返回的结果，表示对应的Service方法的返回值，必须作为当前切面方法的返回值
        Object proceedResult = pjp.proceed();
        // 获取结束时间
        long end = System.currentTimeMillis();
        // 输出
        System.out.println("执行耗时：" + (end - start));
        log.trace("TimerAspect.timer()执行结束，即将返回");
        return proceedResult;
    }

}
